package ru.pb.auth.controllers;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import ru.pb.auth.configs.AuthServerConfig;
import ru.pb.auth.dao.AccountsDAO;
import ru.pb.auth.data.xml.holders.GameServersHolder;
import ru.pb.auth.managers.IPManager;
import ru.pb.auth.model.Account;
import ru.pb.auth.model.GameServerInfo;
import ru.pb.auth.network.clients.AuthClient;
import ru.pb.auth.network.clients.GameServerClient;
import ru.pb.auth.utils.AccountUtils;
import ru.pb.auth.utils.FailedAuthAttempt;
import ru.pb.global.dao.DAOManager;
import ru.pb.global.network.NetList;

public class AuthController
{
	private static AccountsDAO _dao = DAOManager.getDAOImpl(AccountsDAO.class);
	private static Logger _log = Logger.getLogger(AuthController.class);

	private static AuthController _instance;

	private final Map<String, AuthClient> _authServerClients = new HashMap<String, AuthClient>();
	//
	private final Map<String, FailedAuthAttempt> _hackList = new HashMap<String, FailedAuthAttempt>();
	private final Map<String, FailedAuthAttempt> _useIn = new HashMap<String, FailedAuthAttempt>();

	public static enum State
	{
		VALID, //
		WRONG, //
		NOT_PAID, //
		BANNED, //
		IN_USE, //
		IP_ACCESS_DENIED
	}

	public static void load() throws Exception
	{
		if(_instance == null)
		{
			_instance = new AuthController();
		}
		else
		{
			throw new IllegalStateException("AuthController can only be loaded a single time.");
		}
	}

	public static AuthController getInstance()
	{
		return _instance;
	}

	private AuthController() throws Exception
	{

	}

	public boolean isAccountInLoginServer(String a)
	{
		return _authServerClients.containsKey(a);
	}

	public AuthClient removeAuthedLoginClient(String account)
	{
		if(isAccountInLoginServer(account))
			return _authServerClients.remove(account);
		return null;
	}

	public AuthClient getAuthedClient(String account)
	{
		return _authServerClients.get(account);
	}

	public State tryAuthLogin(String account, String password, AuthClient client)
	{
		String ip = client.getIPString();
		State ret = loginValid(account, password, client);

		if(ret != State.VALID)
		{
			FailedAuthAttempt failedAttempt = _hackList.get(ip);
			int failedCount;

			if(failedAttempt == null)
			{
				_hackList.put(ip, new FailedAuthAttempt());
				failedCount = 1;
			}
			else
			{
				failedAttempt.increaseCounter();
				failedCount = failedAttempt.getCount();
			}

			if(failedCount >= AuthServerConfig.AUTH_COUNT_OF_TRY_BEFORE_BAN)
			{
				_log.info("ban ip " + ip);

				IPManager.getInstance().addToListIp(ip, "AUTO BAN SYSTEM", AuthServerConfig.AUTH_TRY_BAN_IN_MINUTES, false);

				_hackList.remove(ip);
			}

			return ret;
		}
		else
		{
			_hackList.remove(ip);

			GameServerInfo gameInfo = getAccountOnGameServer(account);
			AuthClient anotherClient = _authServerClients.get(account);
			if(gameInfo == null && anotherClient == null)
			{
				_authServerClients.put(account, client);
				return State.VALID;
			}
			else if(gameInfo != null)
			{
				FailedAuthAttempt failedAttempt = _useIn.get(account);
				int useCount;

				if(failedAttempt == null)
				{
					_useIn.put(account, new FailedAuthAttempt());
					useCount = 1;
				}
				else
				{
					failedAttempt.increaseCounter();
					useCount = failedAttempt.getCount();
				}

				if(useCount >= AuthServerConfig.AUTH_COUNT_OF_TRY_BEFORE_KICK)
				{
					if(anotherClient != null)
					{
						_authServerClients.remove(account);
						anotherClient.close(true);
					}

					gameInfo.getGameServer().kickByAccount(account, true);

					_authServerClients.put(account, client);

					return State.VALID;
				}
				else
				{
					gameInfo.getGameServer().kickByAccount(account, false);
					return State.IN_USE;
				}
			}
			else if(anotherClient != null)
			{
				_authServerClients.remove(account);
				anotherClient.close(true);
				return State.IN_USE;
			}
		}
		return State.WRONG;
	}

	private GameServerInfo getAccountOnGameServer(String account)
	{
		Collection<GameServerInfo> serverList = GameServersHolder.getInstance().getGameServers();
		for(GameServerInfo gsi : serverList)
		{
			GameServerClient gst = gsi.getGameServer();
			if(gst != null && gst.isAccountInGameServer(account))
			{
				return gsi;
			}
		}
		return null;
	}

	public State loginValid(String user, String password, AuthClient client)
	{
		String ip = client.getIPString();

		// TODO LOG NLogClient.log(EEvent.TRY_AUTH, ip, user == null ? "-" : user, password == null ? "-" : password);

		Account account = _dao.select(user);

		if(account != null)
		{
			if(account.getAllowIPs() != null && !account.getAllowIPs().isEmpty() && !account.getAllowIPs().equals("*"))
			{
				NetList allowedList = new NetList();
				allowedList.LoadFromString(account.getAllowIPs(), ",");
				if( !allowedList.isIpInNets(client.getIPString()))
				{
					return State.IP_ACCESS_DENIED;
				}
			}

			if(account.getPassword().equals(""))
			{
				return State.WRONG;
			}

			boolean banned = false;
			long banTime = account.getBanExpires();

			if(banTime == -1)
			{
				banned = true;
			}
			else if(banTime > 0)
			{
				if(banTime < System.currentTimeMillis() / 1000)
				{
					account.setBanExpires(0);
				}
				else
				{
					banned = true;
				}
			}

			if(banned)
			{
				return State.BANNED;
			}

			if( !account.isPay())
			{
				return State.NOT_PAID;
			}

			if( !AccountUtils.check(password, account.getPassword()))
			{
				return State.WRONG;
			}
		}
		else if(AuthServerConfig.AUTH_CREATE_ACCOUNTS && user != null && user.length() >= 2 && user.length() <= 14)
		{
			account = new Account();
			account.setLogin(user);
			account.setAllowIPs("*");
			account.setLastIP(ip);
			account.setBanExpires(0);
			account.setPassword(AccountUtils.encodePassword(password));
			account.setLastServer(1);
			account.setPay(true);
			account.setAccessLevel(0);
			account.setLastActive(System.currentTimeMillis() / 1000);

			_dao.insert(account);

			return State.VALID;
		}
		if(account != null)
		{
			account.setLastActive(System.currentTimeMillis() / 1000);
			account.setLastIP(ip);

			_dao.updateLastActiveIP(account);

			return State.VALID;
		}
		return State.WRONG;
	}
}